(function($) {
'use strict';
	var _currentSpinnerId = 0;
	
	function _scopedEventName(name, id) {
		return name + '.touchspin_' + id;
	}

	function _scopeEventNames(names, id) 
	{
		return $.map(names, function(name) {
		return _scopedEventName(name, id);
		});
	}

$.fn.TouchSpin = function(options) {
	if (options === 'destroy') 
	{
		this.each(function() {
		var originalinput = $(this),
		originalinput_data = originalinput.data();
		$(document).off(_scopeEventNames([
		'mouseup',
		'touchend',
		'touchcancel',
		'mousemove',
		'touchmove',
		'scroll',
		'scrollstart'], originalinput_data.spinnerid).join(' '));
	});
	return;
	}
	
	var defaults = {
		min: 0,
		max: 100,
		initval: '',
		replacementval: '',
		step: 1,
		decimals: 0,
		stepinterval: 100,
		forcestepdivisibility: 'round', // none | floor | round | ceil
		stepintervaldelay: 500,
		verticalbuttons: false,
		verticalupclass: 'glyphicon glyphicon-chevron-up',
		verticaldownclass: 'glyphicon glyphicon-chevron-down',
		prefix: '',
		postfix: '',
		prefix_extraclass: '',
		postfix_extraclass: '',
		booster: true,
		boostat: 10,
		maxboostedstep: false,
		mousewheel: true,
		buttondown_class: 'btn btn-default',
		buttonup_class: 'btn btn-default',
		buttondown_txt: '-',
		buttonup_txt: '+'
	};

	var attributeMap = {
	min: 'min',
	max: 'max',
	initval: 'init-val',
	replacementval: 'replacement-val',
	step: 'step',
	decimals: 'decimals',
	stepinterval: 'step-interval',
	verticalbuttons: 'vertical-buttons',
	verticalupclass: 'vertical-up-class',
	verticaldownclass: 'vertical-down-class',
	forcestepdivisibility: 'force-step-divisibility',
	stepintervaldelay: 'step-interval-delay',
	prefix: 'prefix',
	postfix: 'postfix',
	prefix_extraclass: 'prefix-extra-class',
	postfix_extraclass: 'postfix-extra-class',
	booster: 'booster',
	boostat: 'boostat',
	maxboostedstep: 'max-boosted-step',
	mousewheel: 'mouse-wheel',
	buttondown_class: 'button-down-class',
	buttonup_class: 'button-up-class',
	buttondown_txt: 'button-down-txt',
	buttonup_txt: 'button-up-txt'
	};
	
return this.each(function() {
var settings,
originalinput = $(this),
originalinput_data = originalinput.data(),
container,
elements,
value,
downSpinTimer,
upSpinTimer,
downDelayTimeout,
upDelayTimeout,
spincount = 0,
spinning = false;
init();

	function init() 
	{
		
		if (originalinput.data('alreadyinitialized')) {
		return;
		}
		
		originalinput.data('alreadyinitialized', true);
		_currentSpinnerId += 1;
		originalinput.data('spinnerid', _currentSpinnerId);
		if (!originalinput.is('input')) 
		{
			console.log('Must be an input.');
			return;
		}
		
		_initSettings();
		_setInitval();
		_checkValue();
		_buildHtml();
		_initElements();
		_hideEmptyPrefixPostfix();
		_bindEvents();
		_bindEventsInterface();
		
		elements.input.css('display', 'block');
	}

	function _setInitval() {
		if (settings.initval !== '' && originalinput.val() === '') {
		originalinput.val(settings.initval);
		}
	}

	function changeSettings(newsettings) {
		_updateSettings(newsettings);
		_checkValue();
		var value = elements.input.val();
		if (value !== '') {
		value = Number(elements.input.val());
		elements.input.val(value.toFixed(settings.decimals));
		}
	}

	function _initSettings() {
		settings = $.extend({}, defaults, originalinput_data, _parseAttributes(), options);
	}

	function _parseAttributes() {
		var data = {};
		$.each(attributeMap, function(key, value) {
		var attrName = 'bts-' + value + '';
		if (originalinput.is('[data-' + attrName + ']')) {
		data[key] = originalinput.data(attrName);
		}
		});
		return data;
	}

	function _updateSettings(newsettings) {
		settings = $.extend({}, settings, newsettings);
	}

	function _buildHtml()
	{
		var initval = originalinput.val(),
		parentelement = originalinput.parent();
		if (initval !== '') {
		initval = Number(initval).toFixed(settings.decimals);
		}
		originalinput.data('initvalue', initval).val(initval);
		originalinput.addClass('form-control');
		if (parentelement.hasClass('input-group')) 
		{
			
			_advanceInputGroup(parentelement);
		}
		else 
		{
			_buildInputGroup();
		}
	}

	function _advanceInputGroup(parentelement) 
	{
		parentelement.addClass('bootstrap-touchspin');
		var prev = originalinput.prev(),
		next = originalinput.next();
		var downhtml,
		uphtml,
		prefixhtml = '<span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span>',
		postfixhtml = '<span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span>';
		if (prev.hasClass('input-group-btn')) {
		downhtml = '<button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button>';
		prev.append(downhtml);
		}
		else {
		downhtml = '<span class="input-group-btn"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button></span>';
		$(downhtml).insertBefore(originalinput);
		}
		if (next.hasClass('input-group-btn')) {
		uphtml = '<button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button>';
		next.prepend(uphtml);
		}
		else {
		uphtml = '<span class="input-group-btn"><button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button></span>';
		$(uphtml).insertAfter(originalinput);
		}
		$(prefixhtml).insertBefore(originalinput);
		$(postfixhtml).insertAfter(originalinput);
		container = parentelement;
	}

	function _buildInputGroup() {
		var html;
		if (settings.verticalbuttons) 
		{
			html = '<div class="input-group bootstrap-touchspin"><span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span><span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span><span class="input-group-btn-vertical"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-up" type="button"><i class="' + settings.verticalupclass + '"></i></button><button class="' + settings.buttonup_class + ' bootstrap-touchspin-down" type="button"><i class="' + settings.verticaldownclass + '"></i></button></span></div>';
		}
		else {
			html = '<div class="input-group bootstrap-touchspin"><span class="input-group-btn"><button class="' + settings.buttondown_class + ' bootstrap-touchspin-down" type="button">' + settings.buttondown_txt + '</button></span><span class="input-group-addon bootstrap-touchspin-prefix">' + settings.prefix + '</span><span class="input-group-addon bootstrap-touchspin-postfix">' + settings.postfix + '</span><span class="input-group-btn"><button class="' + settings.buttonup_class + ' bootstrap-touchspin-up" type="button">' + settings.buttonup_txt + '</button></span></div>';
		}
		container = $(html).insertBefore(originalinput);
		$('.bootstrap-touchspin-prefix', container).after(originalinput);
		if (originalinput.hasClass('input-sm')) {
		container.addClass('input-group-sm');
		}
		else if (originalinput.hasClass('input-lg')) {
		container.addClass('input-group-lg');
		}
	}

	function _initElements() {
		elements = {
		down: $('.bootstrap-touchspin-down', container),
		up: $('.bootstrap-touchspin-up', container),
		input: $('input', container),
		prefix: $('.bootstrap-touchspin-prefix', container).addClass(settings.prefix_extraclass),
		postfix: $('.bootstrap-touchspin-postfix', container).addClass(settings.postfix_extraclass)
		};
	}

function _hideEmptyPrefixPostfix() {
if (settings.prefix === '') {
elements.prefix.hide();
}
if (settings.postfix === '') {
elements.postfix.hide();
}
}

function _bindEvents() {
originalinput.on('keydown', function(ev) {
var code = ev.keyCode || ev.which;
if (code === 38) {
if (spinning !== 'up') {
upOnce();
startUpSpin();
}
ev.preventDefault();
}
else if (code === 40) {
if (spinning !== 'down') {
downOnce();
startDownSpin();
}
ev.preventDefault();
}
});
originalinput.on('keyup', function(ev) {
var code = ev.keyCode || ev.which;
if (code === 38) {
stopSpin();
}
else if (code === 40) {
stopSpin();
}
});
originalinput.on('blur', function() {
_checkValue();
});
elements.down.on('keydown', function(ev) {
var code = ev.keyCode || ev.which;
if (code === 32 || code === 13) {
if (spinning !== 'down') {
downOnce();
startDownSpin();
}
ev.preventDefault();
}
});
elements.down.on('keyup', function(ev) {
var code = ev.keyCode || ev.which;
if (code === 32 || code === 13) {
stopSpin();
}
});
elements.up.on('keydown', function(ev) {
var code = ev.keyCode || ev.which;
if (code === 32 || code === 13) {
if (spinning !== 'up') {
upOnce();
startUpSpin();
}
ev.preventDefault();
}
});
elements.up.on('keyup', function(ev) {
var code = ev.keyCode || ev.which;
if (code === 32 || code === 13) {
stopSpin();
}
});
elements.down.on('mousedown.touchspin', function(ev) {
elements.down.off('touchstart.touchspin'); // android 4 workaround
if (originalinput.is(':disabled')) {
return;
}
downOnce();
startDownSpin();
ev.preventDefault();
ev.stopPropagation();
});
elements.down.on('touchstart.touchspin', function(ev) {
elements.down.off('mousedown.touchspin'); // android 4 workaround
if (originalinput.is(':disabled')) {
return;
}
downOnce();
startDownSpin();
ev.preventDefault();
ev.stopPropagation();
});
elements.up.on('mousedown.touchspin', function(ev) {
elements.up.off('touchstart.touchspin'); // android 4 workaround
if (originalinput.is(':disabled')) {
return;
}
upOnce();
startUpSpin();
ev.preventDefault();
ev.stopPropagation();
});
elements.up.on('touchstart.touchspin', function(ev) {
elements.up.off('mousedown.touchspin'); // android 4 workaround
if (originalinput.is(':disabled')) {
return;
}
upOnce();
startUpSpin();
ev.preventDefault();
ev.stopPropagation();
});
elements.up.on('mouseout touchleave touchend touchcancel', function(ev) {
if (!spinning) {
return;
}
ev.stopPropagation();
stopSpin();
});
elements.down.on('mouseout touchleave touchend touchcancel', function(ev) {
if (!spinning) {
return;
}
ev.stopPropagation();
stopSpin();
});
elements.down.on('mousemove touchmove', function(ev) {
if (!spinning) {
return;
}
ev.stopPropagation();
ev.preventDefault();
});
elements.up.on('mousemove touchmove', function(ev) {
if (!spinning) {
return;
}
ev.stopPropagation();
ev.preventDefault();
});
$(document).on(_scopeEventNames(['mouseup', 'touchend', 'touchcancel'], _currentSpinnerId).join(' '), function(ev) {
if (!spinning) {
return;
}
ev.preventDefault();
stopSpin();
});
$(document).on(_scopeEventNames(['mousemove', 'touchmove', 'scroll', 'scrollstart'], _currentSpinnerId).join(' '), function(ev) {
if (!spinning) {
return;
}
ev.preventDefault();
stopSpin();
});
originalinput.on('mousewheel DOMMouseScroll', function(ev) {
if (!settings.mousewheel || !originalinput.is(':focus')) {
return;
}
var delta = ev.originalEvent.wheelDelta || -ev.originalEvent.deltaY || -ev.originalEvent.detail;
ev.stopPropagation();
ev.preventDefault();
if (delta < 0) {
downOnce();
}
else {
upOnce();
}
});
}

	function _bindEventsInterface() {
		originalinput.on('touchspin.uponce', function() {
		stopSpin();
		upOnce();
		});
		originalinput.on('touchspin.downonce', function() {
		stopSpin();
		downOnce();
		});
		originalinput.on('touchspin.startupspin', function() {
		startUpSpin();
		});
		originalinput.on('touchspin.startdownspin', function() {
		startDownSpin();
		});
		originalinput.on('touchspin.stopspin', function() {
		stopSpin();
		});
		originalinput.on('touchspin.updatesettings', function(e, newsettings) {
		changeSettings(newsettings);
		});
	}
	
	function _forcestepdivisibility(value) {
		switch (settings.forcestepdivisibility) {
		case 'round':
		return (Math.round(value / settings.step) * settings.step).toFixed(settings.decimals);
		case 'floor':
		return (Math.floor(value / settings.step) * settings.step).toFixed(settings.decimals);
		case 'ceil':
		return (Math.ceil(value / settings.step) * settings.step).toFixed(settings.decimals);
		default:
		return value;
		}
	}

	function _checkValue() {
		var val, parsedval, returnval;
		val = originalinput.val();
		if (val === '') {
		if (settings.replacementval !== '') {
		originalinput.val(settings.replacementval);
		originalinput.trigger('change');
		}
		return;
		}
		if (settings.decimals > 0 && val === '.') {
		return;
		}
		parsedval = parseFloat(val);
		if (isNaN(parsedval)) {
		if (settings.replacementval !== '') {
		parsedval = settings.replacementval;
		}
		else {
		parsedval = 0;
		}
		}
		returnval = parsedval;
		if (parsedval.toString() !== val) {
		returnval = parsedval;
		}
		if (parsedval < settings.min) {
		returnval = settings.min;
		}
		if (parsedval > settings.max) {
		returnval = settings.max;
		}
		returnval = _forcestepdivisibility(returnval);
		if (Number(val).toString() !== returnval.toString()) {
		originalinput.val(returnval);
		originalinput.trigger('change');
		}
	}

	function _getBoostedStep() {
		if (!settings.booster) {
		return settings.step;
		}
		else {
		var boosted = Math.pow(2, Math.floor(spincount / settings.boostat)) * settings.step;
		if (settings.maxboostedstep) {
		if (boosted > settings.maxboostedstep) {
		boosted = settings.maxboostedstep;
		value = Math.round((value / boosted)) * boosted;
		}
		}
		return Math.max(settings.step, boosted);
		}
	}
	
	function upOnce() {
		_checkValue();
		value = parseFloat(elements.input.val());
		if (isNaN(value)) {
		value = 0;
		}
		var initvalue = value,
		boostedstep = _getBoostedStep();
		value = value + boostedstep;
		if (value > settings.max) {
		value = settings.max;
		originalinput.trigger('touchspin.on.max');
		stopSpin();
		}
		elements.input.val(Number(value).toFixed(settings.decimals));
		if (initvalue !== value) {
		originalinput.trigger('change');
		}
	}
	
	function downOnce() {
		_checkValue();
		value = parseFloat(elements.input.val());
		if (isNaN(value)) {
		value = 0;
		}
		var initvalue = value,
		boostedstep = _getBoostedStep();
		value = value - boostedstep;
		if (value < settings.min) {
		value = settings.min;
		originalinput.trigger('touchspin.on.min');
		stopSpin();
		}
		elements.input.val(value.toFixed(settings.decimals));
		if (initvalue !== value) {
		originalinput.trigger('change');
		}
	}

	function startDownSpin() {
		stopSpin();
		spincount = 0;
		spinning = 'down';
		originalinput.trigger('touchspin.on.startspin');
		originalinput.trigger('touchspin.on.startdownspin');
		downDelayTimeout = setTimeout(function() {
		downSpinTimer = setInterval(function() {
		spincount++;
		downOnce();
		}, settings.stepinterval);
		}, settings.stepintervaldelay);
	}

	function startUpSpin() {
		stopSpin();
		spincount = 0;
		spinning = 'up';
		originalinput.trigger('touchspin.on.startspin');
		originalinput.trigger('touchspin.on.startupspin');
		upDelayTimeout = setTimeout(function() 
		{
			upSpinTimer = setInterval(function() 
			{
				spincount++;
				upOnce();
			}, settings.stepinterval);
		}, settings.stepintervaldelay);
	}

	function stopSpin() {
		clearTimeout(downDelayTimeout);
		clearTimeout(upDelayTimeout);
		clearInterval(downSpinTimer);
		clearInterval(upSpinTimer);
		switch (spinning) {
		case 'up':
		originalinput.trigger('touchspin.on.stopupspin');
		originalinput.trigger('touchspin.on.stopspin');
		break;
		case 'down':
		originalinput.trigger('touchspin.on.stopdownspin');
		originalinput.trigger('touchspin.on.stopspin');
		break;
		}
		spincount = 0;
		spinning = false;
	}

});
};
})(jQuery);